﻿//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Ext.h".
//-------------------------------------------------------------------

#ifndef _UXS_GL_SHADER_H
#define _UXS_GL_SHADER_H

#pragma once

#include "GLStateManager.h"

namespace UX_STL
{
namespace GLShell
{

	//
	// Shader
	//

	class UX_EXPORT GLShader : public GLBaseObject
	{
	protected:
		GLuint				_uID;
		gl_shader::type		_eType;
		bool				_bCompiled;	// true if compiled successfuly

	public:
		GLShader();
		~GLShader();

		bool Create(gl_shader::type eType);
		void Destroy();

		void Swap(GLShader &cShader);

		bool SetSource(const char *pSource, usize uLength = 0) const;
		bool SetSource(const uni_array< const char *> &aSource) const;
		bool GetSource(string &sSource) const;
		bool SetBinary(const void *pData, usize uDataSize, GLenum eFormat);
		bool Compile();
		bool GetCompilationLog(string &sLog) const;

		gl_shader::type	Type()			const	{ return _eType; }
		GLuint			Id()			const	{ return _uID; }
		bool			IsCompiled()	const	{ return _bCompiled; }

		static bool s_GetBinaryFormats(array<GLenum> &aFormats);
		static bool s_GetPrecisionFormat(gl_shader::type eType, gl_shader_precision::type ePrecision,
										 ivec2 &vRange, int &iPrecision);
	};



	//
	// Shader Program
	//

	class UX_EXPORT GLProgram : public GLBaseObject
	{
	public:
		struct GLAttachedShader
		{
			GLuint			uID;
			gl_shader::type	eType;
			///
			GLAttachedShader(GLuint id = 0, gl_shader::type type = gl_shader::_UNKNOWN): uID(id), eType(type) {}
		};

		struct GLAttribInfo
		{
			string				sName;
			GLuint				uIndex;
			uint				uSize;
			gl_uniform::type	eType;			// without samplers and images
			///
			GLAttribInfo(): uIndex(-1), uSize(0), eType(gl_uniform::_UNKNOWN) {}
		};

		struct GLUniformInfo
		{
			string				sName;
			GLuint				uIndex,
								uBlockIndex;
			uint				uCount,			// array size
								uOffset;
			gl_uniform::type	eType;
			///
			GLUniformInfo(): uIndex(-1), uBlockIndex(-1), uCount(0), uOffset(0), eType(gl_uniform::_UNKNOWN) {}
		};

		struct GLSamplerInfo
		{
			string					sName;
			GLuint					uIndex,
									uBlockIndex;
			uint					uCount,			// array size
									uOffset;
			gl_texture::type		eTarget;
			gl_tex_component::type	eType;
			bool					bShadow;
			///
			GLSamplerInfo():
				uIndex(-1), uBlockIndex(0), uCount(0), uOffset(0), bShadow(false),
				eTarget(gl_texture::_UNKNOWN), eType(gl_tex_component::NONE) {}
		};

		struct GLUniformBlockInfo
		{
			string		sName;
			GLuint		uIndex;
			uint		uSize;
			///
			GLUniformBlockInfo(): uIndex(-1), uSize(0) {}
		};

		struct GLSubroutineInfo
		{
			string			sName;
			GLuint			uIndex;
			gl_shader::type	eShader;
			///
			GLSubroutineInfo(): uIndex(-1), eShader(gl_shader::_UNKNOWN) {}
		};

		struct GLSubroutineUniformInfo
		{
			string			sName;
			uint32_array_t	aIndices;	// indices of supported subroutines
			GLuint			uIndex;
			uint			uSize;		// array size
			gl_shader::type	eShader;
			///
			GLSubroutineUniformInfo(): uIndex(-1), uSize(0), eShader(gl_shader::_UNKNOWN) {}
		};

		typedef array< GLAttachedShader >			shader_array_t;
		typedef array< GLAttribInfo >				attrib_array_t;
		typedef array< GLUniformInfo >				uniform_array_t;
		typedef array< GLSamplerInfo >				sampler_array_t;
		typedef array< GLUniformBlockInfo >			uniformblock_array_t;
		typedef array< GLSubroutineInfo >			subroutine_array_t;
		typedef array< GLSubroutineUniformInfo >	subroutine_uniform_array_t;

	protected:
		GLuint				_uID;
		gl_shader::bits		_eAttached;
		bool				_bLinked		: 1,	// true if linked successfuly
							_bSeparable		: 1,	// to use in program pipeline
							_bRetrievable	: 1;	// can get binary program

	public:
		GLProgram();
		~GLProgram();

		bool Create();
		void Destroy();
		
		void Bind() const;
		void Unbind() const;

		void Swap(INOUT GLProgram &cProgram);

		bool Link();
		bool GetLinkingLog(OUT string &sLog) const;
		bool Validate(string *pLog = null, bool bErrorsOnly = false);

		bool SetBinary(const void *pData, usize uDataSize, GLenum eFormat);
		bool GetBinary(binary_array_t &aBuffer, GLenum &eFormat) const;

		bool Attach(const GLShader *pShader);
		bool Detach(const GLShader *pShader);

		bool GetSubroutineUniforms(OUT subroutine_uniform_array_t &aUniforms) const;
		bool GetBlockUniforms(uint uIndex, OUT uniform_array_t &aUniforms) const;
		bool GetSubroutines(OUT subroutine_array_t &aSubroutines) const;
		bool GetUniformBlocks(OUT uniformblock_array_t &aUBlocks) const;
		bool GetAttributes(OUT attrib_array_t &aAttribs) const;
		bool GetUniforms(OUT uniform_array_t &aUniforms) const;
		bool GetSamplers(OUT sampler_array_t &aSamplers) const;
		bool GetUniformInfo(GLint location, OUT GLUniformInfo &sInfo) const;

		bool GetUniformIndices(const uni_array<const char *> &aNames, OUT uint32_array_t &aIndices) const;

		uint  GetUniformBlockSize(GLuint uBlockIndex)	const;
		GLint GetUniformBlockIndex(const char *pName)	const;

		GLint GetSubroutineIndex(const char *pName, gl_shader::type eType) const;
		GLint GetSubroutineUniform(const char *pName, gl_shader::type eType) const;

		GLint GetUniform(const char *pName)	const;
		GLint GetAttrib(const char *pName)	const;
		
		bool GetUniformValue(GLint iLocation, double *pValue) const;
		bool GetUniformValue(GLint iLocation, float *pValue) const;
		bool GetUniformValue(GLint iLocation, int *pValue) const;
		
		bool GetTransformFeedbackVarying(uint uIndex, OUT uint &uCount, OUT gl_uniform::type &eType, OUT string &sName) const;

		bool BindBuffer(GLuint uUniformBlock, GLuint uBindingIndex) const;
		bool ShaderStorageBlockBinding(uint uBlockIndex, uint uBlockBinding) const;

		bool BindFragData(GLuint uColorBuf, const char *pName) const;			// before linking
		bool BindFragDataI(GLuint uColorBuf, GLuint uIndex, const char *pName) const;
		bool BindAttrib(GLuint uIndex, const char *pName) const;


		uint GetTessVerticesCount()	const;	// == patch size

		bool TransformFeedbackVaryings(const uni_array<const char*> &aNames, gl_tf_mode::type eMode) const;
		bool TransformFeedbackVaryings(uni_c_string sNames, gl_tf_mode::type eMode) const;

		bool GetAttachedShaders(OUT shader_array_t &aShaders) const;
		bool UpdateAttachment();
		
		bool SetPrameters(bool bSeparable, bool bRetrievable);

		gl_shader::bits	ShaderBits()	const	{ return _eAttached; }
		GLuint			Id()			const	{ return _uID; }
		bool			IsLinked()		const	{ return _bLinked; }
		bool			IsSeparable()	const	{ return _bSeparable; }
		bool			IsRetrievable()	const	{ return _bRetrievable; }

		bool DispatchCompute(const uvec3 &vNumGroup) const;
		bool DispatchComputeIndirect(isize iIndirect) const;

		static bool  s_GetBinaryFormats(array<GLenum> &aFormats);

		static usize s_SizeofUniform(gl_uniform::type eType);
		static bool  s_GetUniformBaseType(gl_uniform::type eType, OUT gl_uniform::type &eBase, OUT uint &uCount);
		
		static bool  s_ConvertSamplerToTarget(gl_uniform::type eSampler, OUT gl_texture::type &eTarget, OUT gl_tex_component::type &eType, OUT bool &bShadow);
		static bool  s_ConvertTargetToSampler(gl_texture::type eTarget, gl_tex_component::type eType, bool bShadow, OUT gl_uniform::type &eSampler);
		
	private:
		static void _s_CutArray(string &sStr);
	};



	//
	// Program Pipeline
	//

	class UX_EXPORT GLProgramPipeline : public GLBaseObject
	{
	public:
		struct GLAttachedPrograms
		{
			GLuint			uID;
			gl_shader::bits	eShaderBits;
			///
			GLAttachedPrograms(): uID(0), eShaderBits(gl_shader::_UNKNOWN_BIT) {}
			GLAttachedPrograms(GLuint id, gl_shader::bits bits): uID(id), eShaderBits(bits) {}
		};

		typedef array< GLAttachedPrograms >		attachedprogs_array_t;

	protected:
		gl_shader::bits		_eStages;
		GLuint				_uID;

	public:
		GLProgramPipeline();
		~GLProgramPipeline();

		bool Create();
		void Destroy();

		void Swap(GLProgramPipeline &cPipeline);

		void Bind() const;
		void Unbind() const;

		bool Active(GLProgram *pProgram = null) const;
		bool Attach(GLProgram *pProgram, gl_shader::bits eStages = gl_shader::_UNKNOWN_BIT);
		bool Detach(gl_shader::bits eStages = gl_shader::ALL);

		bool GetInfoLog(string &sLog) const;
		bool GetAttachedPrograms(attachedprogs_array_t &aPrograms) const;
		bool Validate(string *pLog = null, bool bErrorsOnly = false) const;
		
		bool DispatchCompute(const uvec3 &vNumGroup) const;
		bool DispatchComputeIndirect(isize iIndirect) const;

		GLuint			Id()		const	{ return _uID; }
		gl_shader::bits	Stages()	const	{ return _eStages; }
	};
	



	
//---------------------------- GLProgram --------------------------//
/*
=================================================
	установить программу текущей
=================================================
*/
	inline void GLProgram::Bind() const
	{
		SMAN()->UseProgram( _uID );
	}

	inline void GLProgram::Unbind() const
	{
		SMAN()->UseProgram( 0 );
	}

/*
=================================================
	возвращает положение юниформы
=================================================
*/
	inline GLint GLProgram::GetUniform(const char *pName) const
	{
		GLint	ret = -1;
		GL_CALL( ret = glGetUniformLocation( _uID, pName ) );
		return ret;
	}

/*
=================================================
	возвращает положение атрибута
=================================================
*/
	inline GLint GLProgram::GetAttrib(const char *pName) const
	{
		GLint	ret = -1;
		GL_CALL( ret = glGetAttribLocation( _uID, pName ) );
		return ret;
	}
	
/*
=================================================
	возвращает размер юниформ структуры
=================================================
*/
	inline uint GLProgram::GetUniformBlockSize(GLuint uBlockIndex) const
	{
		GLint	i_size = 0;
		GL_CALL( glGetActiveUniformBlockiv( _uID, uBlockIndex, GL_UNIFORM_BLOCK_DATA_SIZE, &i_size ) );
		return i_size;
	}
	
/*
=================================================
	возвращает идентификатор юниформ блока
=================================================
*/
	inline GLint GLProgram::GetUniformBlockIndex(const char *pName) const
	{
		GLint	ret = -1;
		GL_CALL( ret = glGetUniformBlockIndex( _uID, pName ) );
		return ret;
	}
	
/*
=================================================
	возвращает идентификатор подпрограммы
=================================================
*/
	inline GLint GLProgram::GetSubroutineIndex(const char *pName, gl_shader::type eType) const
	{
		GLint	ret = -1;
		GL_CALL( ret = glGetSubroutineIndex( _uID, eType, pName ) );
		return ret;
	}
	
/*
=================================================
	возвращает позицию униформы подпрограммы
=================================================
*/
	inline GLint GLProgram::GetSubroutineUniform(const char *pName, gl_shader::type eType) const
	{
		GLint	ret = -1;
		GL_CALL( ret = glGetSubroutineUniformLocation( _uID, eType, pName ) );
		return ret;
	}
	
/*
=================================================
	возвращает массив индексов юниформ
=================================================
*/
	inline bool GLProgram::GetUniformIndices(const uni_array<const char *> &aNames, uint32_array_t &aIndices) const
	{
		if ( aNames.Count() != aIndices.Count() )
			aIndices.Resize( aNames.Count(), false );

		GL_CALL_R( glGetUniformIndices( _uID, (GLsizei)aNames.Count(), (const GLchar **)aNames.ptr(), aIndices.ptr() ) );
		return true;
	}

/*
=================================================
	передает индекс установленного юниформ буфера
=================================================
*/
	inline bool GLProgram::BindBuffer(GLuint uUniformBlock, GLuint uBindingIndex) const
	{
		GL_CALL_R( glUniformBlockBinding( _uID, uUniformBlock, uBindingIndex ) );
		return true;
	}
	
/*
=================================================
	сопоставляет выходную переменную с индексом draw-буфера
=================================================
*/
	inline bool GLProgram::BindFragData(GLuint uColorBuf, const char *pName) const
	{
		GL_CALL_R( glBindFragDataLocation( _uID, uColorBuf, pName ) );
		return true;
	}
	
	inline bool GLProgram::BindFragDataI(GLuint uColorBuf, GLuint uIndex, const char *pName) const
	{
		GL_CALL_R( glBindFragDataLocationIndexed( _uID, uColorBuf, uIndex, pName ) );
		return true;
	}
	
/*
=================================================
	сопоставляет входную переменную с индексом атрибута вершины
=================================================
*/
	inline bool GLProgram::BindAttrib(GLuint uIndex, const char *pName) const
	{
		GL_CALL_R( glBindAttribLocation( _uID, uIndex, pName ) );
		return true;
	}
	
/*
=================================================
	передает индекс установленного shader storage
	буфера (по аналогии с glUniformBlockBinding)
=================================================
*/
	inline bool GLProgram::ShaderStorageBlockBinding(uint uBlockIndex, uint uBlockBinding) const
	{
		GL_CALL_R( glShaderStorageBlockBinding( _uID, uBlockIndex, uBlockBinding ) );
		return true;
	}

/*
=================================================
	возвращает количество вершин в патче
=================================================
*/
	inline uint GLProgram::GetTessVerticesCount() const
	{
		ASSERT( EnumCmp( ShaderBits(), gl_shader::TESS_CONTROL_BIT | gl_shader::TESS_EVALUATION_BIT ) );

		GLint	i_verts = 0;
		GL_CALL( glGetProgramiv( _uID, GL_TESS_CONTROL_OUTPUT_VERTICES, &i_verts ) );
		return i_verts;
	}

/*
=================================================
	передает имена переменных для записи в
	transform feedback буфер
=================================================
*/
	inline bool GLProgram::TransformFeedbackVaryings(const uni_array<const char*> &aNames, gl_tf_mode::type eMode) const
	{
		/*
		Зарезервированные слова:
			gl_NextBuffer		- переключиться на следующий буфер
			gl_SkipComponents1	- пропустить 1 компоненту
			gl_SkipComponents2	- пропустить 2 компоненты
			gl_SkipComponents3	- пропустить 3 компоненты
			gl_SkipComponents4	- пропустить 4 компоненты
		*/
		GL_CALL_R( glTransformFeedbackVaryings( _uID, (GLsizei)aNames.Count(), (const GLchar **)aNames.ptr(), eMode ) );
		return true;
	}
	
	inline bool GLProgram::TransformFeedbackVaryings(uni_c_string sNames, gl_tf_mode::type eMode) const
	{
		static_array< const char *, 16 >	a_names;
		string								s_names( sNames );
		usize								u_count = 0;

		a_names[u_count++] = s_names.cstr();

		for (usize i = 0; i < s_names.Length(); ++i) {
			if ( s_names[i] == ',' ) {
				s_names[i] = '\0';
				a_names[u_count++] = &s_names[i] + 1;
			}
		}
		return TransformFeedbackVaryings( uni_array<const char*>( a_names.ptr(), u_count ), eMode );
	}
	
/*
=================================================
	возвращает информацию об атрибуте TF
=================================================
*/
	inline bool GLProgram::GetTransformFeedbackVarying(uint uIndex, uint &uCount, gl_uniform::type &eType, string &sName) const
	{
		GLsizei	u_length = 0;
		GLenum	e_type = 0;
		sName.Reserve( 32 );
		GL_CALL_R( glGetTransformFeedbackVarying( _uID, uIndex, (GLsizei)sName.Capacity(), &u_length,
												 (GLsizei *)&uCount, &e_type, sName.ptr() ) );
		sName.SetLength( u_length );
		eType = (gl_uniform::type)e_type;
		return true;
	}

/*
=================================================
	возвращает значения из uniform переменной
=================================================
*/
	inline bool GLProgram::GetUniformValue(GLint iLocation, float *pValue) const
	{
		GL_CALL_R( glGetUniformfv( _uID, iLocation, pValue ) );
		return true;
	}

	inline bool GLProgram::GetUniformValue(GLint iLocation, int *pValue) const
	{
		GL_CALL_R( glGetUniformiv( _uID, iLocation, pValue ) );
		return true;
	}
	
	inline bool GLProgram::GetUniformValue(GLint iLocation, double *pValue) const
	{
		GL_CALL_R( glGetUniformdv( _uID, iLocation, pValue ) );
		return true;
	}
	
/*
=================================================
	запускает выполнение compute шейдера
=================================================
*/
	inline bool GLProgram::DispatchCompute(const uvec3 &vNumGroup) const
	{
		ASSERT( _eAttached == gl_shader::COMPUTE_BIT );
		GL_CALL_R( glDispatchCompute( vNumGroup.x, vNumGroup.y, vNumGroup.z ) );
		return true;
	}

	inline bool GLProgram::DispatchComputeIndirect(isize iIndirect) const
	{
		ASSERT( _eAttached == gl_shader::COMPUTE_BIT );
		GL_CALL_R( glDispatchComputeIndirect( iIndirect ) );
		return true;
	}


	
//------------------------ GLProgramPipeline ----------------------//
/*
=================================================
	биндит и анбиндит program pipeline
=================================================
*/
	inline void GLProgramPipeline::Bind() const
	{
		SMAN()->BindProgramPipeline( _uID );
	}

	inline void GLProgramPipeline::Unbind() const
	{
		SMAN()->BindProgramPipeline( 0 );
	}
	
/*
=================================================
	присоединить программу
=================================================
*/
	inline bool GLProgramPipeline::Attach(GLProgram *pProgram, gl_shader::bits eStages)
	{
		CHECK_ERR( pProgram->IsSeparable() and pProgram->IsLinked() );
		
		if ( eStages == gl_shader::_UNKNOWN_BIT )
			eStages = pProgram->ShaderBits();

		GL_CALL_R( glUseProgramStages( _uID, eStages, pProgram->Id() ) );
		_eStages |= eStages;

		return true;
	}
	
/*
=================================================
	отсоединить программы присоединенные
	к заданным шейдерам
=================================================
*/
	inline bool GLProgramPipeline::Detach(gl_shader::bits eStages)
	{
		GL_CALL_R( glUseProgramStages( _uID, eStages, 0 ) );
		_eStages &= ~eStages;
		return true;
	}
	
/*
=================================================
	активирует программу присоединенную к пайплейну
	для передачи юниформ через glUniform*
=================================================
*/
	inline bool GLProgramPipeline::Active(GLProgram *pProgram) const
	{
		GL_CALL_R( glActiveShaderProgram( _uID, pProgram != null ? pProgram->Id() : 0 ) );
		return true;
	}
	
/*
=================================================
	запускает выполнение compute шейдера
=================================================
*/
	inline bool GLProgramPipeline::DispatchCompute(const uvec3 &vNumGroup) const
	{
		ASSERT( EnumCmp( _eStages, gl_shader::COMPUTE_BIT ) );
		GL_CALL_R( glDispatchCompute( vNumGroup.x, vNumGroup.y, vNumGroup.z ) );
		return true;
	}

	inline bool GLProgramPipeline::DispatchComputeIndirect(isize iIndirect) const
	{
		ASSERT( EnumCmp( _eStages, gl_shader::COMPUTE_BIT ) );
		GL_CALL_R( glDispatchComputeIndirect( iIndirect ) );
		return true;
	}

//-------------------------------------------------------------------
	
}	// GLShell
}	// UX_STL

#endif	// _UXS_GL_SHADER_H